package org.durandj.ystream.client.network;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;

import org.durandj.ystream.client.YStreamClient;
import org.durandj.ystream.client.message.Message.Priority;
import org.durandj.ystream.client.util.Timer;

/**
 * Holds information about the server the client
 * is connected to if it is connected to one at all.
 * This class also works as an interface to the
 * server and handles communication with it.
 * 
 * @author durandj
 * @version 1.0
 *
 */
public class Server
{
	/**
	 * The time out for the server connection.
	 */
	public static long WATCHDOG_TIME = 60000;
	
	/**
	 * The socket connection to the server.
	 */
	private Socket socket;
	
	/**
	 * The packet sender for sending packets to the server.
	 */
	private PacketSender packetSender;
	
	/**
	 * The reader of the socket.
	 */
	private InputStream reader;
	
	/**
	 * The watchdog timer that maintains the connection.
	 */
	private Timer watchdog;
	
	/**
	 * Creates a connection to a YStream Server.
	 * 
	 * @param serverAddress - the address of the server
	 * @param port - the port to communicate to the server with
	 * 
	 * @throws UnknownHostException if the target machine could not be found
	 * @throws IOException something happened when initializing the connection
	 */
	public Server(String serverAddress, int port) throws UnknownHostException, IOException
	{
		// We'll let the message handler class take care of some of the initialization.
		super();
		
		if(serverAddress == null || serverAddress.isEmpty())
			throw new InvalidParameterException("Must give a valid address.");
		
		if(port <= 0)
			throw new InvalidParameterException("Must give a valid port.");
		
		this.socket = new Socket(serverAddress, port);
		this.socket.setSoTimeout(100);
		this.packetSender = new PacketSender(socket);
		this.reader = this.socket.getInputStream();
	}
	
	@Override
	public void finalize()
	{
		 if(socket.isConnected())
		 {
			try
			{
				socket.close();
				packetSender = null;
				watchdog.stop();
			}
			catch(IOException e)
			{
			}
		 }
	}
	
	/**
	 * Gets the address of the server.
	 * 
	 * @return the server address
	 */
	public InetAddress getServerAddress()
	{
		return socket.getInetAddress();
	}
	
	/**
	 * Gets the address of the local machine.
	 * 
	 * @return the local address
	 */
	public InetAddress getLocalAddress()
	{
		return socket.getLocalAddress();
	}
	
	/**
	 * Gets a reference to the socket.
	 * 
	 * @return the socket reference
	 */
	public Socket getSocket()
	{
		return socket;
	}
	
	/**
	 * Gets a reference to the packet sender.
	 * 
	 * @return the packet sender
	 */
	public PacketSender getPacketSender()
	{
		return packetSender;
	}
	
	/**
	 * Gets a reference to the input stream of the socket.
	 * 
	 * @return socket input stream
	 */
	public InputStream getInputStream()
	{
		return reader;
	}
	
	/**
	 * Checks if the server connection is alive.
	 */
	public boolean isAlive()
	{
		return watchdog.isRunning();
	}
	
	/**
	 * Sets up the watchdog.
	 */
	public void setWatchdog()
	{
		watchdog = new Timer(WATCHDOG_TIME, YStreamClient.getClient().getNetworkDaemon(), Priority.HIGH);
		watchdog.start();
	}
	
	/**
	 * Feeds the watchdog timer. This should only be called
	 * after the client has received a message from the server.
	 */
	public void feedWatchdog()
	{
		watchdog.stop();
		new Timer(50);
		watchdog.reset(WATCHDOG_TIME);
		watchdog.start();
	}
	
	/**
	 * Stops the watchdog timer. This should only be called
	 * when the connection to the server needs to be cut.
	 */
	public void killWatchdog()
	{
		watchdog.stop();
	}
}
